package com.huqing.icu.base.redis.aop;

import com.huqing.icu.base.redis.Redis;
import com.huqing.icu.base.redis.annotation.RedisLock;
import com.huqing.icu.enums.BusinessConstants;
import com.huqing.icu.exceptions.BusinessException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Description Redis分布式锁的切面类
 * @Author huqing
 * @Date 2025/1/6 10:06
 **/
public class RedisLockAspect {

    private static final Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);

    @Autowired
    private Redis redis;

    private String getKey(Object[] args, String key) {
        if (args == null || args.length < 1)
            return key;
        return key + args[0].toString();
    }

    @Around("@annotation(com.huqing.icu.base.redis.annotation.RedisLock)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String methodName = proceedingJoinPoint.getSignature().getName();
        Class<?>[] paramsType = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes();
        Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
        Object[] args = proceedingJoinPoint.getArgs();
        Method method;
        try {
            method = targetClass.getMethod(methodName, paramsType);
        } catch (NoSuchMethodException ex) {
            logger.error("{} cannot find method {}! \n", targetClass.getSimpleName(), methodName);
            throw new BusinessException();
        }
        RedisLock annotation = method.getAnnotation(RedisLock.class);

        String uuid = UUID.randomUUID().toString();
        String key = getKey(args, annotation.key());

        Object result;
        try {
            boolean lockAndWait = false;
            try {
                lockAndWait = lockAndWait(key, uuid, annotation.expire(), annotation.waitTime(), annotation.sleepTime());
            } catch (InterruptedException ex) {
                logger.error("{} is interrupted!", Thread.currentThread().getName(), ex);
            }
            if (lockAndWait) {
                logger.info("{} get lock! key: {}, value: {}", Thread.currentThread().getName(), key, uuid);
                try {
                    result = proceedingJoinPoint.proceed();
                } catch (Throwable throwable) {
                    logger.error("{} proceed error!", Thread.currentThread().getName(), throwable);
                    throw throwable;
                }
            } else {
                logger.info("{} wait timeout! key: {}, value: {}", Thread.currentThread().getName(), key, uuid);
                throw new BusinessException(BusinessConstants.REDIS_LOCK_TIMEOUT);
            }
        } finally {
            logger.info("{} release lock! key: {}, value: {}", Thread.currentThread().getName(), key, uuid);
            redis.unlock(key, uuid);
        }
        return result;
    }

    private boolean lockAndWait(String key, String value, long expireTime, long waitTime, long sleepTime) throws InterruptedException {
        while (waitTime >= 0) {
            boolean isLock = redis.lock(key, value, expireTime);
            if (isLock) {
                return true;
            }
            waitTime -= sleepTime;
            TimeUnit.MILLISECONDS.sleep(sleepTime);
        }
        return false;
    }
}
